<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>链判断运算符</title>
  </head>
  <body>
    <script>
      // 编程实务中，如果读取对象内部的某个属性，往往需要判断一下，属性的上层对象是否存在。
      // 读取message.body.user.firstName这个属性，安全的写法是写成下面这样。
      // 错误的写法
      // const firstName = message.body.user.firstName || "default";

      // 正确的写法
      const firstName =
        (message &&
          message.body &&
          message.body.user &&
          message.body.user.firstName) || "default";
      /**
       * 上面例子中，firstName属性在对象的第四层，所以需要判断四次，每一层是否有值。
       * 三元运算符?:也常用语判断对象是否存在。
      */
     var fooInput = myForm.querySelector('input[name=foo]')
     var fooValue = fooInput ? fooInput.value : undefined
     // es2020引入了链判断运算符简化写法
     var firstName = message?.body?.user?.firstName || 'default';
     var fooValue = myForm.querySelector('input[name=foo]')?.value;
     /**
      * 上面代码使用了?.运算符，直接在链式调用的时候判断，左侧的对象是否为null或undefined
      * 如果是的，就不再往下运算，而是返回undefined
      * 下面是判断对象方法是否存在，如果存在就立即执行的例子。
     */
    iterator.return?.()
    /**
     * 上面代码中，iterator.return如果有定义就会调用该方法，否则直接返回undefined,不在执行?.后面的部分。
     * 对于那些可能没有实现的方法，这个运算符尤其有用。
    */
   if (myForm.checkValidity?.() === false) {
     // 表单校验失败
     return;
   }
   /**
    * 上面代码中，老式浏览器的表单对象可能没有checkValidity()这个方法，
    * 这时?.运算符就会返回undefined，判断语句就变成了undefined === false，所以就会跳过下面的代码。
    *
    * 链判断运算符?.有三种写法。
    *
    * ----obj?.prop // 对象属性是否存在
    * ----obj?.[expr] // 同上
    * ----func?.(...args) // 函数或者对象方法是否存在
   */
   // 下面是obj?.[expr]用法的一个例子
   var hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1]
   //上面例子中，字符串的match()方法，如果没有发现匹配会返回null，如果发现匹配会返回一个数组，?.运算符起到了判断的作用
   a?.b // 等同于
   a == null ? undefined : a.b;

   a?.[x] // 等同于
   a == null ? undefined : a[x]

   a?.b()// 等同于
   a == null ? undefined : a.b()
   a?.()// 等同于
   a == null ? undefined : a()
   /**
    * 上面代码中，特别注意后面俩种形式，如果a?.b()和a?.()
    * 如果a?.b()里面的a.b有值，但不是函数，不可调用，那么a?.b()是会报错的。
    * a?.()也是如此，如果a也不是函数也会报错。
    *
    * 使用这个运算符，有几个注意点
    * 1.短路机制
    *   本质上，?.运算符相当于一种短路机制，只要不满足条件，就不再往下执行。
    * 2. 括号的影响 如果属性链有圆括号，链判断运算符对圆括号外部没有影响，只对圆括号内部有影响
   */
  a?.[++x];
  // 等同于
  a == null ? undefined : a[++x];
  // 如果a 为null或者undefined，那么x不会进行递增运算，也就是说，链判断符一旦为假，右侧的表达式就不再求值。
   (a?.b).c; // 等同于
   (a == null ? undefined : a.b).c;
   // 上面代码中?.对圆括号外部没有影响，不管a对象是否存在，圆括号后面的.c总是会执行。
   // 一般来说，使用?.运算符的场合，不应该使用圆括号。
   // (3). 报错场合：以下写法是禁止的，会报错！
   // 构造函数
   new a?.();
   new a?.b();
   // 链判断运算符的右侧有模板字符串
   a?.`{b}`;
   // 链判断运算符 左侧是super
  //  super?.();
   // 链运算符用于赋值运算符左侧
   a?.b = c;
   // 4. 右侧不得为10进制数值
   /**
    * 为了保证兼容以前的代码，允许foo?.3:0被解析成foo ? .3 : 0，因此规定如果?.后面紧跟一个十进制数字，
    * 那么?.不再被看成是一个完整的运算符，
    * 而会按照三元运算符进行处理，也就是说，那个小数点会归属于后面的十进制数字，形成一个小数。
   */
    </script>
  </body>
</html>
